package com.teemor.im.remoting.netty;

import com.teemor.im.common.SemaphoreReleaseOnlyOnce;
import com.teemor.im.common.command.RemoteCommand;
import com.teemor.im.discovery.node.ServerNode;
import com.teemor.im.remoting.ReqCallback;
import lombok.Data;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @ename zhoulk
 * @cname 周李科
 * @date 2020/7/1 9:33
 */
@Data
public class ResponseFuture {

    /**
     * 对应一个请求的序列号
     */
    private String seqId;

    /**
     * 目标节点
     */
    private ServerNode target;

    /**
     * 超时时间
     */
    private long timeoutMillis;

    /**
     * 响应信息
     */
    private volatile RemoteCommand response;

    /**
     * 请求是否发送成功
     */
    private volatile boolean sendReqSuc;

    /**
     * 发送异常
     */
    private volatile Throwable cause;

    /**
     * 回调接口
     */
    private ReqCallback callback;

    /**
     * 请求开始时间
     */
    private long beginTimestamp = System.currentTimeMillis();

    /**
     * 用以同步请求时，阻塞调用线程
     */
    private final CountDownLatch countDownLatch = new CountDownLatch(1);

    /**
     * 通过 CAS 来避免回调方法重复调用
     */
    private final AtomicBoolean executeCallbackOnlyOnce = new AtomicBoolean(false);

    private final SemaphoreReleaseOnlyOnce once;

    public ResponseFuture(String seqId, ServerNode target, long timeoutMillis, ReqCallback callback, SemaphoreReleaseOnlyOnce once) {
        this.seqId = seqId;
        this.target = target;
        this.timeoutMillis = timeoutMillis;
        this.callback = callback;
        this.once = once;
    }

    public boolean isTimeout() {
        long diff = System.currentTimeMillis() - this.beginTimestamp;
        return diff > this.timeoutMillis;
    }

    public RemoteCommand waitResponse(final long timeoutMillis) throws InterruptedException {
        this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
        return this.response;
    }

    public void putResponse(final RemoteCommand responseCommand) {
        this.response = responseCommand;
        this.countDownLatch.countDown();
    }

    public void executeCallback() {
        if (callback != null) {
            if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) {
                callback.invoke(this);
            }
        }
    }

    public void release() {
        if (this.once != null) {
            this.once.release();
        }
    }


}
